<?
/**
 * 	The data caching class.
 *
 *	@copyright	2010 - oneDot only<br>
 *  You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.<br>
 *  The content is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.  see the GNU General Public License for more details.
 *
 *	@author		Koen Betsens <we@onedotonly.be>
 *  @package	Global
 */

/**
 * The data caching class.
 *
 * All external, social or cpu-intensive data is stored in timed entries. Once the lifetime cycle is exceeded, the entries will be updated (and latter ones deleted).    
 *
 * <code>
 * global $CACHE = new CACHE( time, limit );
 * </code>
 *
 * @package		Global
 *
 * @return		Object	DB session
 */
class CACHE{
	
	/**
	 * Default refresh rate
	 * @var	int
	 */
	 public $time = 15;
	 
	 /**
	 * Default store amount
	 * @var	int
	 */
	 public $limit = 24;
	
	/**
	 * Updated objects
	 * @var	array
	 */
	 public $updated = array();
	
	/**
	 * Constructs the Cache object
	 *
	 * @param 	int 	$time		Life cycle in minutes. Defaults to 15 min
	 * @param 	int 	$limit		The amount of entries, older entries get deleted. Defaults to 24. Zero ( 0 ) equals "no delete".
	 * @return	object
	 */
 	function __construct( $time = 15, $limit = 24 ){
		
		$this->time		= (int) $time;
		$this->limit	= (int) $limit;

	}
	
	//////////////////// PUBLIC CACHE FUNCTIONS
	
	/**
	 * Bare cache request
	 *
	 * Returns untimed result. For timed cahce results, use the test() function
	 *
	 * @param 	string  $querystring		query string
	 * @param 	int  	$return		return query result
	 * @return	resource	the unfiltered query result, if $return is true
	 */	
	public function filter ( $type, $parentid, $meta = null, $limit = false, $is_array = false ) {
		global $DB;
		
		
		if( $response = $DB->query( "select cache.cache from cache where " . $this->condition( $type, $parentid, $meta, $limit? $limit : $this->limit, 0 )))
			
			while ( $row = mysql_fetch_array( $response, MYSQL_ASSOC))
					if( $row[ "cache" ])
						$cache[] = json_decode( $row[ "cache" ], $is_array );

		return $cache;
	}
	
	
	/**
	
	 */
	public function test ( $type, $parentid, $meta = null, $limit = false, $is_array = false ) {
		global $DB;
		
		if( $response = $DB->query( "select cache.cache, cache.parentid from cache where " . $this->condition( $type, $parentid, $meta, $limit? $limit : $this->limit, 1 )))
			
			while ( $row 		= mysql_fetch_array( $response, MYSQL_ASSOC))
					$cache[]	= json_decode( $row[ "cache" ], $is_array );

		return $cache;
	}
	
	/**
	
	 */
	public function test_objects ( $type, $conditions = array(), $limit = false ) {
		global $DB;
		
		$this->type = $type;
		
		$where			= array( "active", "time_to_sec( timediff( now(), llog )) <= " . ( $this->time *60 ));
		
		foreach( $conditions as $key=> $condt )
			$where[]	= $this->having_sql( $type, $key, $condt );

		$cache = $DB->filter_objects ( $type, implode( " && ", $where ) . " order by flog desc limit " . ( $limit? $limit : $this->limit ));
		
		return $cache? $cache : array();
	}


	/**

	 */
	public function filter_fill ( $type, $group, $conditions = array(), $limit = false ) {
		global $DB;
		
		$limit		= $limit? $limit : $this->limit;
		$found		= count( $group );
		
		if( $found >= $limit ) return $group;
		
			$where	= array( "active" );
			$limitstr	= "$found, " . ( $limit - $found);
		
			if( $conditions ) 
				foreach( $conditions as $key=> $condt )
					$where[] = $this->having_sql( $type, $key, $condt );

			if(	$cache = $DB->filter_objects ( $type, implode( " && ", $where ) . " order by llog desc, flog desc limit $limitstr" ))
				$group = array_merge( $group, $cache );
			
			$DB->query( "update " . $DB->type( $type, "table" ) . " set llog = now() where type=" . $DB->type( $type ) . " && " . implode( " && ", $where ) . " order by flog desc limit $limit", false );
	
		return	$group;
	}
	
	
	public function insert_objects ( $type, $objects, $unique_key = "id", $user_key = "user", $update_meta = false ) {
		global $DB;
		
		$table	= $DB->type( $type, "table" );
		$typeid	= $DB->type( $type );
		$cache	= array();
		
		foreach( $objects as $object )
				 
				 if( 	!$ob = $DB->get_object( $type, $this->having_sql( $type, $unique_key, $object->{ $unique_key })))
					 	$inserts[] = $object;
				 else { $updates[] = $ob->id; 
						
						if(	$update_meta
						&&	json_decode( $metastring = str_replace( "'", ".", json_encode( $object ))) != $ob->get( "meta" )) $ob->set( "meta", $metastring, false );
				}
#		Update.	
		if( $updates )
			$DB->query( "update $table set llog = now() where ID in(" . implode( ",", $updates ) . ")", false );		 
					 
#		Insert.
		if( $inserts ) {
			foreach( $inserts as $n=> $object )
					 $inserts[ $n ] = implode( "','", array( $typeid,
															 date( "Y-m-d H:i:s", $object->date? strtotime( $object->date ): time()),
															 $object->{ $user_key },
															 $meta[] = str_replace( "'", ".", json_encode( $object ))));

			$DB->query( "insert into $table( `type`, `flog`, `creator`, `meta` ) values ('" . implode( "'),('", $inserts ) . "')", false );
		}
		
#		Delete spill.
		if( $this->limit < $DB->count( $type, "active > 0" )) {

			$date		= $DB->get_key( $type, "active order by flog asc", "flog" );
			$nullcount	= $DB->count( $type, "!active && flog >= '$date'" );

			$DB->delete( $type, "active < 2 order by flog desc", false, ( $this->limit + $nullcount ) . ", 1000" );
		}

#		Create Objects
		if( 	$inserts )
				$cache = $DB->filter_objects ( $type, "active && meta = '" . implode( "' OR meta = '", $meta ) . "' order by llog desc limit " . count( $meta ));

		return	$cache;
	}


	/**
	
	 */
	public function having_sql( $type, $key, $value ) {
		global  $DB;
		
		return 	( $key != "id" && $DB->column_exists( $type, $key, 1 ))?
				"`$key` = '$value'"
		:		"meta regexp '\"$key\":\"?" . str_replace( array("+","?","ˆ","$","|","\\"), ".", $value ) . "\"?'";
	
	}


	/**
	
	 */
	public function insert ( $type, $cache, $parentid, $unique, $meta = null, $date = null ) {
		global $DB;
		
		if( $this->limit < $this->count( $type, $parentid, $meta ))
			$DB->query( "delete from cache where " . $this->condition( $type, $parentid, $meta, $this->limit . ", 1000" ), false );
		
		$meta[ "unique" ]	= $unique;

		if( !$this->count( $type, $parentid, $meta ))
			
			 $DB->query( "insert into cache( type, parentid, flog, llog, meta, cache ) values ( " . $DB->type( $type ) . ", $parentid, " . date( "'Y-m-d H:i:s'" , $date? $date : time()) . ", now(), '" . json_encode( $meta ) . "', '" . json_encode( $cache ) . "' )", false );
		else $DB->query( "update cache set llog = now() where " . $this->condition( $type, $parentid, $meta, 1 ), false );
	}
	
	
	/**
	
	 */
	public function count ( $type, $parentid, $meta = null, $time = false ) {
		global $DB;
		
		if( $response = $DB->query("select count(*) from cache where " . $this->condition( $type, $parentid, $meta, 1000, $time )))
			$row = mysql_fetch_array( $response, MYSQL_ASSOC);


		return (int) $row[ "count(*)" ];

	}
	
	
	/**
	
	 */
	private function condition ( $type, $parentid, $meta = null, $limit = CACHE::limit, $time = null ) {
		global $DB;
		
		if( $time )
			$condt[] = " && time_to_sec( timediff( now(), llog )) <= " . ( $this->time *60 );
		
		if( $meta )
			foreach( $meta as $key => $val )
				
				$condt[] = " && meta regexp '\"$key\":\"?" . str_replace( array("+","?","ˆ","$","|","\\"), ".", $val ) . "\"?'";
		
		$parentid = ( $parentid )?
			" && parentid = $parentid" :
			" && (select parent.active from " . $DB->type( $type, "table" ) . " as parent where parent.ID=cache.parentid)";
		
		return "type =" . $DB->type( $type ) . $parentid . implode( "", $condt ) . " order by flog desc limit $limit";
	}
	
	
	function report( $report ) {
	
		if( $_GET[ 'fresh' ]) echo "-------- CACHE {$this->type} ($report) ---------- \n";
	}
}

?>